using System;
using System.Collections.Generic;
using System.Text;

namespace CodePlex.Resourceful.Amazon.SimpleDB
{
    public class SimpleDBClient : Client
    {
        private readonly string _accessKeyID;
        private readonly string _secretAccessKey;

        public SimpleDBClient(string accessKeyID, string secretAccessKey, bool useSSL)
            : base(new Uri((useSSL ? "https" : "http") + "://sdb.amazonaws.com/"))
        {
            _accessKeyID = accessKeyID;
            _secretAccessKey = secretAccessKey;
        }



        public ClientResponse CreateDomain(string domainName)
        {
            return this.Do("CreateDomain", delegate(NameValuePairs nvp)
            {
                nvp["DomainName"] = domainName;
            });
        }

        public ClientResponse DeleteDomain(string domainName)
        {
            return this.Do("DeleteDomain", delegate(NameValuePairs nvp)
            {
                nvp["DomainName"] = domainName;
            });
        }

        public ClientResponse ListDomains(int? maxNumberOfDomains, string nextToken)
        {
            return this.Do("ListDomains", delegate(NameValuePairs nvp)
            {
                this.AddOptional(nvp, "MaxNumberOfDomains", maxNumberOfDomains);
                this.AddOptional(nvp, "NextToken", nextToken);
            });
        }

        public ClientResponse PutAttributes(string domainName, string itemName, NameValuePairs attributes)
        {
            // TODO multi-valued attributes
            // TODO attribute-level replace
            return this.Do("PutAttributes", delegate(NameValuePairs nvp)
            {
                nvp["DomainName"] = domainName;
                nvp["ItemName"] = itemName;

                Utils.ForEachWithIndex(attributes, delegate(KeyValuePair<string, string> kvp, int i)
                {
                    nvp["Attribute." + i + ".Name"] = kvp.Key;
                    nvp["Attribute." + i + ".Value"] = kvp.Value;
                });
            });
        }

        public ClientResponse GetAttributes(string domainName, string itemName, params string[] attributeNames)
        {
            return this.Do("GetAttributes", delegate(NameValuePairs nvp)
            {
                nvp["DomainName"] = domainName;
                nvp["ItemName.0"] = itemName;
                nvp["ItemName.1"] = "Item234";

                Utils.ForEachWithIndex(attributeNames, delegate(string attributeName, int i)
                {
                    nvp["AttributeName." + i] = attributeName;
                });
            });
        }

        public ClientResponse DeleteAttributes(string domainName, string itemName, NameValuePairs attributes)
        {
            attributes = attributes ?? new NameValuePairs();

            // TODO multi-valued attributes
            return this.Do("DeleteAttributes", delegate(NameValuePairs nvp)
            {
                nvp["DomainName"] = domainName;
                nvp["ItemName"] = itemName;

                Utils.ForEachWithIndex(attributes, delegate(KeyValuePair<string, string> kvp, int i)
                {
                    nvp["Attribute." + i + ".Name"] = kvp.Key;
                    nvp["Attribute." + i + ".Value"] = kvp.Value;
                });
            });
        }

        public ClientResponse Query(string domainName, string queryExpression, int? maxNumberOfItems, string nextToken)
        {
            return this.Do("Query", delegate(NameValuePairs nvp)
            {
                nvp["DomainName"] = domainName;
                AddOptional(nvp, "QueryExpression", queryExpression);
                AddOptional(nvp, "MaxNumberOfItems", maxNumberOfItems);
                AddOptional(nvp, "NextToken", nextToken);
            });
        }










        private void AddOptional(NameValuePairs nvp, string name, object value)
        {
            if (value != null)
            {
                nvp[name] = value.ToString();
            }
        }

        private ClientResponse Do(string action, Operation<NameValuePairs> fn)
        {
            NameValuePairs nvp = new NameValuePairs();
            nvp["AWSAccessKeyId"] = _accessKeyID;
            nvp["Timestamp"] = Utils.Iso8601Date(DateTime.Now);
            nvp["Version"] = "2007-11-07";
            nvp["Action"] = action;
            fn(nvp);
            SetSignatureVersion1(nvp);
            return this.GetRepresentation("", nvp);
        }

        private void SetSignatureVersion0(NameValuePairs nvp)
        {
            nvp["SignatureVersion"] = "0";
            string stringToSign = nvp["Action"] + nvp["Timestamp"];
            SetSignature(nvp, stringToSign);
        }


        private void SetSignatureVersion1(NameValuePairs nvp)
        {
            nvp["SignatureVersion"] = "1";
            string stringToSign = Utils.Join("", Utils.ConvertAll<string, string>(Utils.Sort(nvp.Keys), delegate(string key)
            {
                return key + nvp[key];
            }));
            SetSignature(nvp, stringToSign);
        }

        private void SetSignature(NameValuePairs nvp, string stringToSign)
        {
            nvp["Signature"] = Utils.ToBase64(Utils.ToHMACSHA1(Encoding.UTF8, _secretAccessKey, stringToSign));
        }

    }

}
